1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.data.ini;
12 public import hip.api.data.ini;
13 import hip.api.data.asset;
14 import hip.util.conv:to;
15 
16 
17 class HipINI : HipAsset, IHipIniFile
18 {
19     IniBlock[string] _blocks;
20     string path;
21     bool configFound = false;
22     bool _noError = true;
23     string[] errors;
24 
25     bool noError() const{return _noError;}
26     ref IniBlock[string] blocks(){return _blocks;}
27     const(string[]) getErrors() const{return cast(const)errors;}
28 
29     this()
30     {
31         super("HipINI");
32         _typeID = assetTypeID!HipINI;
33     }
34 
35     bool loadFromMemory(string data, string path)
36     {
37         this.path = path;
38         if(data != "")
39             configFound = true;
40 
41         for(int i = 0; i < data.length; i++)
42         {
43             char c = data[i];
44             if(c == ';' || c == '#')
45             {
46                 while(i < data.length && data[++i] != '\n'){}
47                 continue;
48             }
49             else if(c == '[')
50             {
51                 import hip.util.string : replaceAll, trim, splitRange;
52                 import hip.util.algorithm:put;
53                 int captureStart = i+1;
54                 while(i < data.length)
55                 {
56                     i++;
57                     if(i >= data.length)
58                         return true;
59                     else if(data[i] == ']')
60                         break;
61                 }
62 
63                 IniBlock block;
64                 block.name = data[captureStart..i];
65                 string capture = "";
66                 captureStart = i+1;
67                 //Read until finding a key.
68                 while(++i < data.length && data[i] != '['){}
69                 capture = data[captureStart..i];
70 
71                 
72                 foreach(l; capture.splitRange("\n"))
73                 {
74                     l = l.trim;
75                     if(l == "")
76                         continue;
77                     string k, v;
78                     l.splitRange("=").put(&k, &v);
79                     if(v == "")
80                     {
81                         errors~= "No value for key '"~k~"'";
82                         _noError = false;
83                         break;
84                     }
85                     string name = k.replaceAll(' ', "");
86                     block.vars[name] =  IniVar(name, formatValue(v));
87                 }
88                 blocks[block.name] = block;
89                 i--;
90             }
91         }
92         return true;
93     }
94 
95     /**
96     *   Simple parser for the .conf or .ini files commonly found.
97     */
98     static HipINI parse(string content, string path = "")
99     {
100         HipINI f = new HipINI();
101         if(!f.loadFromMemory(content, path))
102             return null;
103         return f;
104     }
105 
106     IniVar* getIniVar(string varPath)
107     {
108         import hip.util.string:splitRange;
109         import hip.util.algorithm;
110         string accessorA, accessorB;
111         varPath.splitRange(".").put(&accessorA, &accessorB);
112         if(accessorB == "")
113             return null;
114         IniBlock* b = (accessorA in blocks);
115         if(b is null)
116             return null;
117         return (accessorB in *b);
118     }
119 
120     override void onFinishLoading(){}
121     override void onDispose(){}
122     override bool isReady() const {return errors.length == 0;}
123 
124 }
125 
126 /**
127 *   Remove comments and spaces from the Value from KeyValue pair
128 */
129 private string formatValue(string unformattedValue)
130 {
131     string ret;
132     foreach(ch; unformattedValue)
133     {
134         if(ch == '#' || ch == ';') //Remove comments
135             break;
136         if(ch == ' ') //Remove spaces for to!int and friends working correctly
137             continue;
138         ret~= ch;
139     }
140     return ret;
141 }